#include "EPoller.h"
#include "Channel.h"
#include "Eventloop.h"
#include "log/Logger.h"
#include "utility/assertion.h"

#include <cstring>
#include <errno.h>
#include <poll.h>
#include <sys/epoll.h>
#include <unistd.h>

namespace _ {

// On Linux, the constants of poll(2) and epoll(4)
// are expected to be the same.
static_assert(EPOLLIN == POLLIN);
static_assert(EPOLLPRI == POLLPRI);
static_assert(EPOLLOUT == POLLOUT);
static_assert(EPOLLRDHUP == POLLRDHUP);
static_assert(EPOLLERR == POLLERR);
static_assert(EPOLLHUP == POLLHUP);

const int kNew = -1;
const int kAdded = 1;
const int kDeleted = 2;

}

EPoller::EPoller(Eventloop *loop):
    Poller{loop},
    _epollfd{::epoll_create1(EPOLL_CLOEXEC)},
    _events{kInitEventListSize},
    _channels{}
{
}

EPoller::~EPoller() {
    ::close(_epollfd);
}

Utility::timepoint EPoller::poll(int timeout, ChannelList *activeChannels) {
    int numEvents = ::epoll_wait(_epollfd, _events.data(), static_cast<int>(_events.size()), timeout);
    auto now = Utility::now();
    if (numEvents > 0) {
        LOG_VERBOSE << numEvents << " events happend";
        _fillActiveChannels(numEvents, activeChannels);
        if (Cast::EQ(numEvents, _events.size())) {
            _events.resize(_events.size() * 2);
        }
    }
    else if (numEvents == 0) {
        LOG_VERBOSE << "no events happened";
    }
    else {
        LOG_SYSERROR << "EPoller::poll()";
    }
    return now;
}

void EPoller::_fillActiveChannels(int numEvents, ChannelList *activeChannels) const {
    assert(Cast::LE(numEvents, _events.size()));
    for (int i = 0; i < numEvents; i++) {
        auto channel = static_cast<Channel *>(_events[i].data.ptr);
#ifndef NDDEBUG
        int fd = channel->fd();
        auto it = _channels.find(fd); (void)it;
        assert(it != _channels.end());
        assert(it->second == channel);
#endif
        channel->set_revents(_events[i].events);
        activeChannels->push_back(channel);
    }
}


void EPoller::update_channel(Channel *channel) {
    Poller::assert_in_loopthread();
    LOG_VERBOSE << "fd = " << channel->fd() << " events = " << channel->events();
    const int index = channel->index();
    if (index == _::kNew || index == _::kDeleted) {
        int fd = channel->fd();
        if (index == _::kNew) {
            assert(_channels.find(fd) == _channels.end());
            _channels[fd] = channel;
        }
        else {
            assert(_channels.find(fd) != _channels.end());
            assert(_channels[fd] == channel);
        }
        channel->set_index(_::kAdded);
        _update(EPOLL_CTL_ADD, channel);
    }
    else {
        // update existing one with EPOLL_CTL_MOD/DEL
        int fd = channel->fd(); (void)fd;
        assert(_channels.find(fd) != _channels.end());
        assert(_channels[fd] == channel);
        assert(index == _::kAdded);
        if (channel->is_non_event()) {
            _update(EPOLL_CTL_DEL, channel);
            channel->set_index(_::kDeleted);
        }
        else {
            _update(EPOLL_CTL_MOD, channel);
        }
    }
}

void EPoller::remove_channel(Channel *channel) {
    Poller::assert_in_loopthread();
    int fd = channel->fd();
    LOG_VERBOSE << "fd = " << fd;
    assert(_channels.find(fd) != _channels.end());
    assert(_channels[fd] == channel);
    assert(channel->is_non_event());
    int index = channel->index();
    assert(index == _::kAdded || index == _::kDeleted);
    auto  n = _channels.erase(fd); (void)n;
    assert(n == 1);

    if (index == _::kAdded) {
        _update(EPOLL_CTL_DEL, channel);
    }
    channel->set_index(_::kNew);

}

void EPoller::_update(int operation, Channel *channel) {
    epoll_event event;
    ::bzero(&event, sizeof(event));
    event.events = channel->events();
    event.data.ptr = channel;
    int fd = channel->fd();
    if (::epoll_ctl(_epollfd, operation, fd, &event) < 0) {
        if (operation == EPOLL_CTL_DEL) {
            LOG_SYSERROR << "epoll_ctl op = " << operation << ", fd = " << fd;
        }
        else {
            LOG_SYSFATAL << "epoll_ctl op = " << operation << ", fd = " << fd;
        }
    }
}